﻿using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;

namespace Hellquest
{
    /// <summary>
    /// Component enablin collsion detection. Uses the attached sprite dimensions by default
    /// </summary>
    public class Collider : Component
    {
        private bool isSpriteBased = true;
        private Rectangle baseAABB;
        private Rectangle BaseAABB
        {
            get
            {
                return new Rectangle((int)((gameObject.rigidbody != null ? gameObject.rigidbody.NewPosition.X : gameObject.transform.WorldPosition.X) + (isSpriteBased ? -gameObject.sprite.Origin.X : baseAABB.X)),
                                     (int)((gameObject.rigidbody != null ? gameObject.rigidbody.NewPosition.Y : gameObject.transform.WorldPosition.Y) + (isSpriteBased ? -gameObject.sprite.Origin.Y : baseAABB.Y)),
                                     isSpriteBased && gameObject.sprite != null ? gameObject.sprite.Width : baseAABB.Width,
                                     isSpriteBased && gameObject.sprite != null ? gameObject.sprite.Height : baseAABB.Height);
            }
        }
        private Rectangle mAABB;
        private Vector2 aabbLocation;

        public Rectangle AABB
        {
            get { return mAABB; }
        }

        public OBB OrientedBoundingBox
        {
            get;
            private set;
        }

        private List<Vector2> Mesh;

        public Collider()
        {
            Mesh = new List<Vector2>();
        }

        public Collider(Rectangle _baseAABB)
        {
            Mesh = new List<Vector2>();
            baseAABB = _baseAABB;
            isSpriteBased = false;
        }

        public Collider(List<Vector2> _mesh)
        {
            Mesh = _mesh;
        }

        #region Component Inheritance

        public override void OnAdd(GameObject _gameObject)
        {
            base.OnAdd(_gameObject);
            gameObject.collider = this;
            RecalcAABB();
            RecalcOBB();

            PhysicsEngine.Instance.AddCollider(this);
        }

        public override void OnRemove()
        {
            base.OnRemove();
            gameObject.collider = null;
            PhysicsEngine.Instance.RemoveCollider(this);
        }

        public override void LateUpdate(GameTime _gameTime)
        {
            base.LateUpdate(_gameTime);
            UpdateAABB();
            UpdateOBB();


            List<Vector2> screenPoints = new List<Vector2>(OrientedBoundingBox.Corners);
            for (int i = 0; i < screenPoints.Count; i++)
                screenPoints[i] = Graphics.Instance.MainCamera.WorldToScreen(screenPoints[i]);

            Graphics.Instance.DrawGizmo(screenPoints);
            Graphics.Instance.DrawGizmo(Graphics.Instance.MainCamera.WorldToScreen(AABB));
        }

        #endregion

        #region private methods

        #endregion

        #region public methods

        public void UpdateOBB()
        {
            if (!gameObject.transform.RotationChanged)
            {
                if (gameObject.transform.PositionChanged)
                {
                    OrientedBoundingBox.Offset(gameObject.transform.PositionDiff);
                }
            }
            else
            {
                RecalcOBB();
            }
        }

        public void UpdateAABB()
        {
            if (!gameObject.transform.RotationChanged)
            {
                if (gameObject.transform.PositionChanged)
                {
                    aabbLocation += gameObject.transform.PositionDiff;
                    mAABB.Location = new Point((int)(aabbLocation.X),
                                               (int)(aabbLocation.Y));
                }
            }
            else
            {
                RecalcAABB();
            }
        }

        private void RecalcOBB()
        {
            Vector2 OBBPos;
            if (gameObject.sprite != null && gameObject.sprite.IsCentered)
            {
                OBBPos = Position;
            }
            else
            {
                float cosAngle = (float)Math.Cos(gameObject.transform.Rotation);
                float sinAngle = (float)Math.Sin(gameObject.transform.Rotation);
                OBBPos = new Vector2((gameObject.sprite.Width / 2 * cosAngle) - (gameObject.sprite.Height / 2 * sinAngle),
                                             (gameObject.sprite.Height / 2 * cosAngle) + (gameObject.sprite.Width / 2 * sinAngle)) +
                                             Position;
            }

            OrientedBoundingBox = new OBB(OBBPos,
                                          gameObject.sprite != null ? gameObject.sprite.Width : 0,
                                          gameObject.sprite != null ? gameObject.sprite.Height : 0,
                                          gameObject.transform.Rotation);
        }

        private void RecalcAABB()
        {
            Matrix rotation = Matrix.CreateRotationZ(-gameObject.transform.Rotation);
            float[,] m = new float[2, 2];
            m[0, 0] = rotation.M11;
            m[0, 1] = rotation.M12;
            m[1, 0] = rotation.M21;
            m[1, 1] = rotation.M22;

            float[] min = new float[2];
            float[] max = new float[2];

            min[0] = -gameObject.sprite.Origin.X;
            min[1] = -gameObject.sprite.Origin.Y;

            max[0] = BaseAABB.Width - gameObject.sprite.Origin.X;
            max[1] = BaseAABB.Height - gameObject.sprite.Origin.Y;

            float[] newMin = new float[2];
            float[] newMax = new float[2];

            for (int i = 0; i < 2; i++)
            {
                for (int j = 0; j < 2; j++)
                {
                    float e = m[i, j] * min[j];
                    float f = m[i, j] * max[j];

                    if (e < f)
                    {
                        newMin[i] += e;
                        newMax[i] += f;
                    }
                    else
                    {
                        newMin[i] += f;
                        newMax[i] += e;
                    }
                }
            }

            mAABB = new Rectangle((int)(newMin[0] + BaseAABB.X + gameObject.sprite.Origin.X), (int)(newMin[1] + BaseAABB.Y + gameObject.sprite.Origin.Y), (int)newMax[0] - (int)newMin[0], (int)newMax[1] - (int)newMin[1]);
            aabbLocation = new Vector2(mAABB.Location.X, mAABB.Location.Y);
        }



        public bool Intersects(Collider _other)
        {
            if (_other == null)
                return false;

            Vector2 projection = Vector2.Zero;
            if (AABB.Intersects(_other.AABB))
            {
                if (OrientedBoundingBox.Intersects(_other.OrientedBoundingBox, ref projection))
                {
                    return true;
                }
            }
            return false;
        }

        public bool Intersects(Collider _other, ref Vector2 _projection)
        {
            if (_other == null)
                return false;

            if (AABB.Intersects(_other.AABB))
            {
                if (OrientedBoundingBox.Intersects(_other.OrientedBoundingBox, ref _projection))
                {
                    return true;
                }
            }
            return false;
        }

        public Vector2 GetAABBProjection(Collider _other)
        {
            int yOverlap = 0, xOverlap = 0;

            if (this.AABB.Left < _other.AABB.Right && this.AABB.Left > _other.AABB.Left)
                xOverlap = _other.AABB.Right - this.AABB.Left;
            else if (_other.AABB.Left < this.AABB.Right && _other.AABB.Left > this.AABB.Left)
                xOverlap = -(this.AABB.Right - _other.AABB.Left);

            if (this.AABB.Top < _other.AABB.Bottom && this.AABB.Top > _other.AABB.Top)
                yOverlap = _other.AABB.Bottom - this.AABB.Top;
            else if (_other.AABB.Top < this.AABB.Bottom && _other.AABB.Top > this.AABB.Top)
                yOverlap = -(this.AABB.Bottom - _other.AABB.Top);

            if (Math.Abs(yOverlap) < Math.Abs(xOverlap) || (xOverlap == 0 && yOverlap != 0))
                return new Vector2(0, yOverlap);
            else
                return new Vector2(xOverlap, 0);
        }

        #endregion
    }
}
